package com.wh1200.mybatis.xmlless.model.base

import com.wh1200.mybatis.xmlless.AnnotatedElementDetailInfo
import com.wh1200.mybatis.xmlless.enums.TestType
import com.wh1200.mybatis.xmlless.model.*
import com.wh1200.mybatis.xmlless.model.component.TestConditionDeclaration
import com.wh1200.mybatis.xmlless.model.component.TestConditionGroup
import com.wh1200.mybatis.xmlless.model.criteria.CriteriaParameter
import com.wh1200.mybatis.xmlless.util.ScriptUtils

/**
 *
 * @author 吴昊
 * @date 2023/12/14 22:29
 * @since v4.0.0
 * @version 1.0
 */
abstract class BaseCriteria(
    val parameters: List<CriteriaParameter>,
    protected val methodInfo: MethodInfo,
    var append: ConditionConnector
) {

  protected var forHaving = false

  open fun getTests(parameterTest: TestInfo?): String {
    if (parameterTest != null) {
      return resolveTests(parameterTest)
    }
    val tests = TestConditionGroup()
    parameters.firstOrNull()?.also {
      tests.addAll(resolveTestsFromType(it))
    }
    return tests.toSql()
  }

  abstract fun toSqlWithoutTest(): String

  abstract fun getTableNames(): List<String>

  abstract fun getColumns(extraJoins: List<JoinInfo> = listOf()): List<SelectColumn>

  fun wrapWithTests(sql: String, parameterTest: TestInfo?, ifTag: String = "if"): String {
    val tests = getTests(parameterTest)
    if (tests.isNotBlank()) {
      return ScriptUtils.convertIf("\t" + sql, tests, true, ifTag)
    }
    return sql
  }

  abstract fun getProperty(): String

  abstract fun toSql(): String

  fun toSql(wrapWithIf: Boolean): String {
    if (wrapWithIf) {
      return toSql()
    }
    return toSqlWithoutTest()
  }

  abstract fun toSql(parameterTest: TestInfo?, ifTag: String = "if"): String

  fun forHaving() {
    forHaving = true
  }

  abstract fun clone(parameters: List<CriteriaParameter>): BaseCriteria

  abstract fun clone(append: ConditionConnector): BaseCriteria

  protected fun getOnlyParameter(): AnnotatedElementDetailInfo? {
    return parameters.firstOrNull()?.element
  }

  protected fun realParams(): List<ParamHolder> {
    if (parameters.isEmpty()) {
      return listOf(ParamHolder(getProperty(), null))
    }
    return parameters.map {
      if (it.specificValue) {
        ParamHolder(null, it.name.toString())
      } else {
        ParamHolder(it.name.toString(), null)
      }
    }
  }

  private fun resolveTests(parameterTest: TestInfo): String {
    return parameterTest.getExpression(parameters.filter { it.element != null }, methodInfo)
  }

  private fun resolveTestsFromType(parameter: CriteriaParameter): List<TestConditionDeclaration> {
    if (parameter.element == null || parameter.element.isNoIfWrap) {
      return listOf()
    }
    val tests = mutableListOf<TestConditionDeclaration>()
    val realParams = realParams()
    if (parameter.element.testTypes.isNotEmpty()) {
      return parameter.element.testTypes.map { testType ->
        realParams.map { TestConditionDeclaration(it.toString(), testType) }
      }.flatten()
    }
    if (!parameter.isNotNull) {
      tests.addAll(realParams.map { TestConditionDeclaration(it.toString(), TestType.NotNull) })
    }
    if (parameter.element.isString && !parameter.isNotBlank) {
      tests.addAll(realParams.map { TestConditionDeclaration(it.toString(), TestType.NotBlank) })
    }
    if (parameter.element.isCollection && !parameter.isNotEmpty) {
      tests.addAll(realParams.map { TestConditionDeclaration(it.toString(), TestType.NotEmpty) })
    }
    return tests.toList()
  }

}
